package com.ruoyi.flowable.service.impl;

import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.List;

import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamReader;

import com.ruoyi.flowable.service.IFlowableModelService;
import com.ruoyi.flowable.service.IModelInfoService;
import org.apache.commons.lang3.StringUtils;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.editor.language.json.converter.BaseBpmnJsonConverter;
import org.flowable.editor.language.json.converter.BpmnJsonConverter;
import org.flowable.editor.language.json.converter.util.CollectionUtils;
import org.flowable.engine.repository.Deployment;
import org.flowable.idm.api.User;
import org.flowable.ui.common.service.exception.BadRequestException;
import org.flowable.ui.common.util.XmlUtil;
import org.flowable.ui.modeler.domain.AbstractModel;
import org.flowable.ui.modeler.domain.Model;
import org.flowable.ui.modeler.model.ModelRepresentation;
import org.flowable.validation.ProcessValidator;
import org.flowable.validation.ProcessValidatorFactory;
import org.flowable.validation.ValidationError;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;


import com.fasterxml.jackson.databind.node.ObjectNode;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.flowable.flowable.ServiceFactory;
import com.ruoyi.flowable.domain.ModelInfo;
import com.ruoyi.flowable.model.ModelInfoVo;
import com.ruoyi.flowable.service.IFlowableBpmnService;

@Component
public class FlowableBpmnServiceImpl extends ServiceFactory implements IFlowableBpmnService {

    private static final String BPMN_EXTENSION = ".bpmn";
    private static final String BPMN20_XML_EXTENSION = ".bpmn20.xml";

    @Autowired
    protected BpmnXMLConverter bpmnXMLConverter;

    @Autowired
    private ProcessValidatorFactory processValidatorFactory;

    @Autowired
    protected BpmnJsonConverter bpmnJsonConverter;

    @Autowired
    private IFlowableModelService flowableModelService;

    @Autowired
    private IModelInfoService modelInfoService;


    @Override
    public AjaxResult importBpmnModel(String modelId, String fileName, InputStream modelStream, User user) {
        AjaxResult ajaxResult;
        Model processModel = modelService.getModel(modelId);
        if (StringUtils.isBlank(fileName)) {
            fileName = processModel.getKey() + BPMN_EXTENSION;
        }
        if (fileName.endsWith(BPMN_EXTENSION) || fileName.endsWith(BPMN20_XML_EXTENSION)) {
            try {
                XMLInputFactory xif = XmlUtil.createSafeXmlInputFactory();
                InputStreamReader xmlIn = new InputStreamReader(modelStream, StandardCharsets.UTF_8);
                XMLStreamReader xtr = xif.createXMLStreamReader(xmlIn);
                BpmnModel bpmnModel = bpmnXMLConverter.convertToBpmnModel(xtr);
                bpmnModel.getMainProcess().setId(processModel.getKey());
                bpmnModel.setTargetNamespace(BaseBpmnJsonConverter.NAMESPACE);
                if (CollectionUtils.isEmpty(bpmnModel.getProcesses())) {
                    ajaxResult = AjaxResult.error("No process found in definition " + fileName);
                    return ajaxResult;
                }
                if (bpmnModel.getLocationMap().size() == 0) {
                    ajaxResult = AjaxResult.error("No required BPMN DI information found in definition " + fileName);
                    return ajaxResult;
                }
                ProcessValidator processValidator = processValidatorFactory.createDefaultProcessValidator();
                List<ValidationError> validationErrors = processValidator.validate(bpmnModel);
                if (CollectionUtils.isNotEmpty(validationErrors)) {
                    StringBuffer message = new StringBuffer();
                    validationErrors.forEach(validationError -> message.append(validationErrors.toString()));
                    ajaxResult = AjaxResult.error(message.toString());
                    return ajaxResult;
                }
                ObjectNode modelNode = bpmnJsonConverter.convertToJson(bpmnModel);
                this.setProcessPropertiesToKey(modelNode, processModel.getKey());
                AbstractModel savedModel = modelService.saveModel(modelId, processModel.getName(), processModel.getKey(),
                        processModel.getDescription(), modelNode.toString(), false,
                        null, user.getId());

                ajaxResult = AjaxResult.success(savedModel.getId());
                return ajaxResult;
            } catch (BadRequestException e) {
                throw e;
            } catch (Exception e) {
                ajaxResult = AjaxResult.error("bpmn.js failed for " + fileName + ", error message " + e.getMessage());
                return ajaxResult;
            }
        } else {
            ajaxResult = AjaxResult.error("Invalid file name, only .bpmn and .bpmn20.xml files are supported not " + fileName);
            return ajaxResult;
        }
    }

    private void setProcessPropertiesToKey(ObjectNode modelNode, String key) {
        ObjectNode objectNode = (ObjectNode) modelNode.get("properties");
        objectNode.put("process_id", key);
        objectNode.put("processid", key);
    }

    @Override
    public AjaxResult stopBpmn(String modelId) {

        return AjaxResult.success("停止服务！");
    }

    @Override
    public AjaxResult publishBpmn(String modelId) {


        return AjaxResult.success();
    }


    @Override
    public AjaxResult createInitBpmn(ModelInfo modelInfo, SysUser user) {
        ModelRepresentation modelRepresentation = new ModelRepresentation();
        modelRepresentation.setModelType(AbstractModel.MODEL_TYPE_BPMN);
        modelRepresentation.setKey(modelInfo.getModelKey());
        modelRepresentation.setName(modelInfo.getName());
        modelRepresentation.setTenantId(modelInfo.getAppSn());
        modelRepresentation.setLastUpdated(new Date());
        AjaxResult returnVo = flowableModelService.createModel(modelRepresentation, user);
        return returnVo;
    }

    @Override
    public Deployment deployBpmn(ModelInfo modelInfo) {
        Model model = modelService.getModel(modelInfo.getModelId());
        BpmnModel bpmnModel = modelService.getBpmnModel(model);
        Deployment deploy = repositoryService.createDeployment()
                .name(model.getName())
                .key(model.getKey())
                .category(modelInfo.getCategoryCode())
                .tenantId(model.getTenantId())
                .addBpmnModel(model.getKey() + BPMN_EXTENSION, bpmnModel)
                .deploy();
        return deploy;
    }

    @Override
    public ModelInfoVo loadBpmnXmlByModelId(String modelId) {
        Model model = modelService.getModel(modelId);
        byte[] bpmnXML = modelService.getBpmnXML(model);
        String streamStr = new String(bpmnXML);
        ModelInfoVo modelInfoVo = new ModelInfoVo();
        modelInfoVo.setModelId(modelId);
        modelInfoVo.setModelName(model.getName());
        modelInfoVo.setModelKey(model.getKey());
        modelInfoVo.setFileName(model.getName());
        modelInfoVo.setModelXml(streamStr);
        return modelInfoVo;
    }

    @Override
    public ModelInfoVo loadBpmnXmlByModelKey(String modelKey) {
        if (StringUtils.isNotBlank(modelKey)) {
            ModelInfo info = modelInfoService.getModelInfoByModelKey(modelKey);
            if (info != null) {
                return this.loadBpmnXmlByModelId(info.getModelId());
            }
        }
        return null;
    }
}
